// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using iPanel.Components.Layout;
using iPanel.ConsoleLogs;
using iPanel.Extensions;
using iPanel.Model.Otlp;
using iPanel.Resources;
using Aspire.Hosting.ConsoleLogs;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using Microsoft.JSInterop;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons;

namespace iPanel.Components.Pages;

public sealed partial class ConsoleLogs : ComponentBase, IAsyncDisposable, IPageWithSessionAndUrlState<ConsoleLogs.ConsoleLogsViewModel, ConsoleLogs.ConsoleLogsPageState>
{
    // 这里应该使用RPC 实现，控制台逻辑


    private sealed class ConsoleLogsSubscription
    {
        private static int s_subscriptionId;

        private readonly CancellationTokenSource _cts = new();
        private readonly int _subscriptionId = Interlocked.Increment(ref s_subscriptionId);

        public required string Name { get; init; }
        public Task? SubscriptionTask { get; set; }

        public CancellationToken CancellationToken => _cts.Token;
        public int SubscriptionId => _subscriptionId;
        public void Cancel() => _cts.Cancel();
    }

    [Inject]
    public required IOptions<DashboardOptions> Options { get; init; }

    [Inject]
    public required OtlpConsoleLogService OtlpConsoleLogService { get; init; }

    [Inject]
    public required ILocalStorage LocalStorage { get; init; }

    [Inject]
    public required ISessionStorage SessionStorage { get; init; }

    [Inject]
    public required NavigationManager NavigationManager { get; init; }

    [Inject]
    public required ILogger<ConsoleLogs> Logger { get; init; }

    [Inject]
    public required IStringLocalizer<iPanel.Resources.ConsoleLogs> Loc { get; init; }

    [Inject]
    public required IStringLocalizer<ControlsStrings> ControlsStringsLoc { get; init; }

    [Inject]
    public required IJSRuntime JS { get; init; }

    [Inject]
    public required DashboardCommandExecutor DashboardCommandExecutor { get; init; }

    [Inject]
    public required ConsoleLogsManager ConsoleLogsManager { get; init; }

    [Inject]
    public required BrowserTimeProvider TimeProvider { get; init; }

    [Inject]
    public required PauseManager PauseManager { get; init; }

    [CascadingParameter]
    public required ViewportInformation ViewportInformation { get; init; }

    [Parameter]
    public string? ResourceName { get; set; }

    private readonly CancellationTokenSource _resourceSubscriptionCts = new();
    private ImmutableList<SelectViewModel<ResourceTypeDetails>>? _resources = null;
    private CancellationToken _resourceSubscriptionToken;
    private Task? _resourceSubscriptionTask;
    private ConsoleLogsSubscription? _consoleLogsSubscription;
    internal LogEntries _logEntries = null!;
    private LogEntries _noLogEntries = new(1);
    private readonly object _updateLogsLock = new object();

    // UI
    private SelectViewModel<ResourceTypeDetails> _noSelection = null!;
    private AspirePageContentLayout? _contentLayout;

    // State
    private bool _showTimestamp = true;
    private bool _isTimestampUtc = false; // 使用本地时间
    public ConsoleLogsViewModel PageViewModel { get; set; } = null!;

    public string BasePath => DashboardUrls.ConsoleLogBasePath;
    public string SessionStorageKey => BrowserStorageKeys.ConsoleLogsPageState;
    public int MaxConsoleLogCount { get; set; } = 500;
    protected override async Task OnInitializedAsync()
    {
        _resourceSubscriptionToken = _resourceSubscriptionCts.Token;
        _logEntries = new(MaxConsoleLogCount);
        _noSelection = new() { Id = null, Name = ControlsStringsLoc[nameof(ControlsStrings.LabelNone)] };
        PageViewModel = new ConsoleLogsViewModel { SelectedOption = _noSelection, Status = Loc[nameof(iPanel.Resources.ConsoleLogs.ConsoleLogsLoadingResources)] };
        await ConsoleLogsManager.EnsureInitializedAsync();
        var loadingTcs = new TaskCompletionSource();

        await TrackResourceSnapshotsAsync();

        // Wait for resource to be selected. If selected resource isn't available after a few seconds then stop waiting.
        try
        {
            await loadingTcs.Task.WaitAsync(TimeSpan.FromSeconds(5), _resourceSubscriptionToken);
            Logger.LogDebug("Loading task completed.");
        }
        catch (OperationCanceledException)
        {
            Logger.LogDebug("Load task canceled.");
        }
        catch (Exception ex)
        {
            Logger.LogWarning(ex, "Load timeout while waiting for resource {ResourceName}.", ResourceName);
            PageViewModel.Status = Loc[nameof(iPanel.Resources.ConsoleLogs.ConsoleLogsLogsNotYetAvailable)];
        }

        async Task TrackResourceSnapshotsAsync()
        {
            UpdateResourcesList();

            // Set loading task result if the selected resource is already in the snapshot or there is no selected resource.
            if (ResourceName != null)
            {
                var selectedResource = _resources.FirstOrDefault(option => option.Name == ResourceName);
                if (selectedResource is not null)
                    SetSelectedResourceOption(selectedResource);
                else
                    loadingTcs.TrySetResult();
            }
            else
            {
                Logger.LogDebug("No resource selected.");
                loadingTcs.TrySetResult();
            }
        }

        void SetSelectedResourceOption(SelectViewModel<ResourceTypeDetails> resource)
        {
            Debug.Assert(_resources is not null);
            PageViewModel.SelectedOption = _resources.Single(option => option.Name == ResourceName);
            Logger.LogDebug("Selected console resource from name {ResourceName}.", ResourceName);
            loadingTcs.TrySetResult();
        }
    }
    protected override async Task OnParametersSetAsync()
    {
        Logger.LogDebug("Initializing console logs view model.");
        if (await this.InitializeViewModelAsync())
        {
            return;
        }

        var selectedResourceName = PageViewModel.SelectedOption?.Name;
        if (!string.Equals(selectedResourceName, _consoleLogsSubscription?.Name, StringComparisons.ResourceName))
        {
            Logger.LogDebug("New resource {ResourceName} selected.", selectedResourceName);

            ConsoleLogsSubscription? newConsoleLogsSubscription = null;
            if (PageViewModel.SelectedOption.Id is not null)
            {
                newConsoleLogsSubscription = new ConsoleLogsSubscription { Name = selectedResourceName };
                Logger.LogDebug("Creating new subscription {SubscriptionId}.", newConsoleLogsSubscription.SubscriptionId);

                if (Logger.IsEnabled(LogLevel.Debug))
                {
                    newConsoleLogsSubscription.CancellationToken.Register(state =>
                    {
                        var s = (ConsoleLogsSubscription)state!;
                        Logger.LogDebug("Canceling subscription {SubscriptionId} to {ResourceName}.", s.SubscriptionId, s.Name);
                    }, newConsoleLogsSubscription);
                }
            }

            if (_consoleLogsSubscription is { } currentSubscription)
            {
                currentSubscription.Cancel();
                _consoleLogsSubscription = newConsoleLogsSubscription;

                await TaskHelpers.WaitIgnoreCancelAsync(currentSubscription.SubscriptionTask);
            }
            else
            {
                _consoleLogsSubscription = newConsoleLogsSubscription;
            }

            Logger.LogDebug("Creating new log entries collection.");

            _logEntries = PageViewModel.SelectedOption.Id is null ? _noLogEntries : PageViewModel.SelectedOption.Id.LogEntries;
            if (newConsoleLogsSubscription is not null)
            {
                _logEntries = PageViewModel.SelectedOption.Id.LogEntries;
                LoadLogs(newConsoleLogsSubscription);
            }
        }
    }

    // 更新
    private async Task UpdateResourcesList()
    {
        var logParser = new LogParser();
        _resources = [_noSelection];
        var nodes = OtlpConsoleLogService
            .GetNodes()
            .Select(item =>
            {
                var id = ResourceTypeDetails.CreateSingleton(item.Key, item.Key);
                id.Channel = item.Value.Key;
                id.LogEntries = item.Value.Value;
                return new SelectViewModel<ResourceTypeDetails>
                {
                    Name = item.Key,
                    Id = id
                };
            }).ToArray();
        _resources = _resources.AddRange(nodes);
        OtlpConsoleLogService.OnNewNodes((node, cahnnel, entries) =>
        {
            var id = ResourceTypeDetails.CreateSingleton(node, node);
            id.Channel = cahnnel;
            id.LogEntries = entries;
            _resources = _resources.Add(new SelectViewModel<ResourceTypeDetails>
            {
                Name = node,
                Id = id
            });
        }, _resourceSubscriptionCts.Token);
    }

    private void LoadLogs(ConsoleLogsSubscription newConsoleLogsSubscription)
    {
        Logger.LogDebug("Starting log subscription {SubscriptionId}.", newConsoleLogsSubscription.SubscriptionId);
        var consoleLogsTask = Task.Run(async () =>
        {
            newConsoleLogsSubscription.CancellationToken.ThrowIfCancellationRequested();

            Logger.LogDebug("Subscribing to console logs with subscription {SubscriptionId} to resource {ResourceName}.", newConsoleLogsSubscription.SubscriptionId, newConsoleLogsSubscription.Name);

            //var subscription = DashboardClient.SubscribeConsoleLogs(newConsoleLogsSubscription.Name, newConsoleLogsSubscription.CancellationToken);

            PageViewModel.Status = Loc[nameof(iPanel.Resources.ConsoleLogs.ConsoleLogsWatchingLogs)];
            await InvokeAsync(StateHasChanged);

            try
            {
                var logParser = new LogParser();
                try
                {
                    while (await PageViewModel.SelectedOption.Id.Channel.Reader.WaitToReadAsync(newConsoleLogsSubscription.CancellationToken))
                    {
                        // Set the base line number using the reported line number of the first log line.
                        _logEntries.BaseLineNumber ??= 1;
                        while (PageViewModel.SelectedOption.Id.Channel.Reader.TryRead(out string content))
                        {
                            var logEntry = logParser.CreateLogEntry(content, false);
                            // Check if log entry is not displayed because of pause.
                            if (_logEntries.ProcessPauseFilters(logEntry))
                            {
                                continue;
                            }
                            _logEntries.InsertSorted(logEntry);
                            if (PageViewModel.SelectedOption.Id is null) break;
                        }
                        await InvokeAsync(StateHasChanged);
                    }
                }
                catch (NullReferenceException)
                {
                }
            }
            finally
            {
                Logger.LogDebug("Subscription {SubscriptionId} finished watching logs for resource {ResourceName}.", newConsoleLogsSubscription.SubscriptionId, newConsoleLogsSubscription.Name);

                // If the subscription is being canceled then a new one could be starting.
                // Don't set the status when finishing because overwrite the status from the new subscription.
                if (!newConsoleLogsSubscription.CancellationToken.IsCancellationRequested)
                {
                    PageViewModel.Status = Loc[nameof(iPanel.Resources.ConsoleLogs.ConsoleLogsFinishedWatchingLogs)];
                    await InvokeAsync(StateHasChanged);
                }
            }
        });

        newConsoleLogsSubscription.SubscriptionTask = consoleLogsTask;
    }

    private async Task HandleSelectedOptionChangedAsync()
    {
        try
        {
            await this.AfterViewModelChangedAsync(_contentLayout, waitToApplyMobileChange: false);
        }
        catch (Exception ex)
        {
            throw;
        }
    }

    private async Task StopAndClearConsoleLogsSubscriptionAsync()
    {
        if (_consoleLogsSubscription is { } consoleLogsSubscription)
        {
            consoleLogsSubscription.Cancel();
            await TaskHelpers.WaitIgnoreCancelAsync(consoleLogsSubscription.SubscriptionTask);

            _consoleLogsSubscription = null;
        }
    }

    private async Task ClearConsoleLogs(ApplicationKey? key)
    {
        _logEntries.Clear(keepActivePauseEntries: true);
        await InvokeAsync(StateHasChanged);
    }

    private void OnPausedChanged(bool isPaused)
    {
        var timestamp = DateTime.UtcNow;
        PauseManager.SetConsoleLogsPaused(isPaused, timestamp);

        if (PageViewModel.SelectedOption != null)
        {
            lock (_updateLogsLock)
            {
                if (isPaused)
                {
                    _logEntries.InsertSorted(LogEntry.CreatePause(timestamp));
                }
                else
                {
                    var pause = _logEntries.GetEntries().Last().Pause;
                    if (pause is null) return;
                    Debug.Assert(pause is not null);
                    pause.EndTime = timestamp;
                }
            }
        }
    }

    public async ValueTask DisposeAsync()
    {
        _resourceSubscriptionCts.Cancel();
        _resourceSubscriptionCts.Dispose();
        await TaskHelpers.WaitIgnoreCancelAsync(_resourceSubscriptionTask);
        await StopAndClearConsoleLogsSubscriptionAsync();
    }

    public class ConsoleLogsViewModel
    {
        public required string Status { get; set; }
        public required SelectViewModel<ResourceTypeDetails> SelectedOption { get; set; }
    }

    public record ConsoleLogsPageState(string? SelectedResource);

    public record ConsoleLogConsoleSettings(bool ShowTimestamp, bool IsTimestampUtc);

    public Task UpdateViewModelFromQueryAsync(ConsoleLogsViewModel viewModel)
    {
        if (_resources is not null && ResourceName is not null)
        {
            var selectedOption = _resources.FirstOrDefault(c => ResourceName == c.Name) ?? _noSelection;

            viewModel.SelectedOption = selectedOption;
            viewModel.Status ??= Loc[nameof(iPanel.Resources.ConsoleLogs.ConsoleLogsLogsNotYetAvailable)];
        }
        else
        {
            viewModel.SelectedOption = _noSelection;
            viewModel.Status = Loc[nameof(iPanel.Resources.ConsoleLogs.ConsoleLogsNoResourceSelected)];
        }

        return Task.CompletedTask;
    }

    public string GetUrlFromSerializableViewModel(ConsoleLogsPageState serializable)
    {
        return DashboardUrls.ConsoleLogsUrl(serializable.SelectedResource);
    }

    public ConsoleLogsPageState ConvertViewModelToSerializable()
    {
        return new ConsoleLogsPageState(PageViewModel.SelectedOption?.Name);
    }
}
